/*
 * guest context on VMM
 *
 * COPYRIGHT (C) 2013-2014, Shanghai Real-Thread Technology Co., Ltd
 *
 *  This file is part of RT-Thread (http://www.rt-thread.org)
 *
 *  All rights reserved.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Change Logs:
 * Date           Author       Notes
 * 2013-11-04     Grissiom     add comment
 */

#include <rthw.h>
#include <rtthread.h>
#include <interrupt.h>

#include <log_trace.h>
#include <vmm.h>

#include "vmm_context.h"

struct rt_vmm_share_layout rt_vmm_share SECTION(".vmm.share");

volatile struct vmm_context* _vmm_context = RT_NULL;

void vmm_context_init(void* context_addr)
{
	_vmm_context = (struct vmm_context*)context_addr;
	rt_memset((void*)_vmm_context, 0x00, sizeof(struct vmm_context));
	/* When loading RT-Thread, the IRQ on the guest should be disabled. */
	_vmm_context->virq_status = 1;
}

#ifdef RT_VMM_USING_DOMAIN
unsigned long guest_domain_val SECTION(".bss.share");
unsigned long vmm_domain_val SECTION(".bss.share");
/* some RT-Thread code need to be called in the guest
 * context(rt_thread_idle_excute for example). To simplify the code, we need a
 * "super" domain mode to have access of both side. The code executed in super
 * domain mode is restricted and should be harmless. */
unsigned long super_domain_val SECTION(".bss.share");
void vmm_context_init_domain(struct vmm_domain* domain)
{
	asm volatile("mrc p15, 0, %0, c3, c0\n" : "=r"(guest_domain_val));

	rt_kprintf("Linux domain: kernel: %d, user: %d, io: %d\n"
	           "VMM domain: vmm: %d, share: %d\n",
	           domain->kernel, domain->user, domain->io,
	           domain->vmm, domain->vmm_share);

	if(domain->kernel == domain->vmm ||
	        domain->io     == domain->vmm) {
		rt_kprintf("VMM and the guest share the same domain\n");
		super_domain_val = vmm_domain_val = guest_domain_val;
		return;
	}

	vmm_domain_val = guest_domain_val;

	/* become client to our own territory */
	vmm_domain_val |= (1 << (domain->vmm * 2)) | (1 << (domain->vmm_share * 2));

	super_domain_val = vmm_domain_val;
	/* super domain has access to both side */
	super_domain_val |= (1 << (domain->kernel * 2)) | (1 << (domain->user * 2));

	rt_kprintf("Original DAC: 0x%08x\n", guest_domain_val);
}

unsigned long vmm_context_enter_domain(unsigned long domain_val)
{
	unsigned long old_domain;

	asm volatile("mrc p15, 0, %0, c3, c0\n" : "=r"(old_domain));
	asm volatile("mcr p15, 0, %0, c3, c0\n" : :"r"(domain_val) : "memory");

	return old_domain;
}

void vmm_context_restore_domain(unsigned long domain_val)
{
	asm volatile("mcr p15, 0, %0, c3, c0\n" : :"r"(domain_val) : "memory");
}
#endif

void vmm_virq_pending(int irq)
{
	/* when running this piece of code, the guest is already suspended. So it's
	 * safe to set the bits without locks. */
	_vmm_context->virq_pending[irq / 32] |= (1 << (irq % 32));
	_vmm_context->virq_pended = 1;
	/* mask this IRQ in host */
	rt_hw_interrupt_mask(irq);
}

void vmm_virq_update(void)
{
	if((!_vmm_context->virq_status) &&
	        (_vmm_context->virq_pended)) {
		rt_hw_interrupt_trigger(RT_VMM_VIRQ_TRIGGER);
	}
}

/** check the guest IRQ status
 *
 * @return 0 on guest should handle IRQ, -1 on should restore the guest context
 * normally.
 */
int vmm_virq_check(void)
{
	if((!_vmm_context->virq_status) &&
	        (_vmm_context->virq_pended)) {
		return 0;
	}

	return -1;
}

/* 10 = len("%08x, ") */
static char _vmbuf[10 * ARRAY_SIZE(_vmm_context->virq_pending)];
void vmm_dump_virq(void)
{
	int i, s;

	vmm_info("---- virtual IRQ ----\n");
	vmm_info("  status: %08x,   pended: %08x, pending:\n",
	         _vmm_context->virq_status, _vmm_context->virq_pended);

	for(s = 0, i = 0; i < ARRAY_SIZE(_vmm_context->virq_pending); i++) {
		s += rt_snprintf(_vmbuf + s, sizeof(_vmbuf) - s,
		                 "%08x, ", _vmm_context->virq_pending[i]);
	}

	vmm_info("%.*s\n", sizeof(_vmbuf), _vmbuf);
	vmm_info("---- virtual IRQ ----\n");
}

int vmm_virq_coherence_ok(void)
{
	int i, res;
	int should_pend = 0;

	for(i = 0; i < ARRAY_SIZE(_vmm_context->virq_pending); i++) {
		should_pend |= _vmm_context->virq_pending[i];
	}

	res = (_vmm_context->virq_pended == !!should_pend);

	if(!res) {
		vmm_info("--- %x %x, %x\n",
		         _vmm_context->virq_pended, should_pend, !!should_pend);
	}

	return res;
}

extern struct rt_thread vmm_thread;

void vmm_show_guest_reg(void)
{
	struct rt_hw_stack* sp = vmm_thread.sp;
#ifdef RT_VMM_USING_DOMAIN
	unsigned long old_domain;

	old_domain = vmm_context_enter_domain(super_domain_val);
#endif

	vmm_info("CPSR: %08x, PC: %08x, LR: %08x, SP: %08x\n",
	         sp->cpsr, sp->pc, sp->lr, sp + 1);

#ifdef RT_VMM_USING_DOMAIN
	vmm_context_restore_domain(old_domain);
#endif
}

void vmm_dump_domain(void)
{
	unsigned long dac;

	asm volatile("mrc p15, 0, %0, c3, c0\n" : "=r"(dac));
	vmm_info("current DAC: %08x\n", dac);
#ifdef RT_VMM_USING_DOMAIN
	vmm_info("guest DAC: %08x, RTT DAC: %08x, super DAC: %08x\n",
	         guest_domain_val, vmm_domain_val, super_domain_val);
#endif
}

void vmm_show_guest(void)
{
	vmm_show_guest_reg();
	vmm_dump_virq();
	vmm_dump_domain();
}

#ifdef RT_USING_FINSH
	#include <finsh.h>
	FINSH_FUNCTION_EXPORT_ALIAS(vmm_show_guest, vmm, show vmm status);
#endif

static int _bad_cpsr(unsigned long cpsr)
{
	int bad = 1;

	switch(cpsr & MODEMASK) {
		case USERMODE:
		case FIQMODE:
		case IRQMODE:
		case SVCMODE:
#ifdef CPU_HAS_MONITOR_MODE
		case MONITORMODE:
#endif
		case ABORTMODE:
#ifdef CPU_HAS_HYP_MODE
		case HYPMODE:
#endif
		case UNDEFMODE:
		case MODEMASK:
			bad = 0;
			break;
	};

	return bad;
}

void vmm_verify_guest_status(struct rt_hw_stack* sp)
{
	int dump_vmm = 0;
	unsigned long cpsr;
#ifdef RT_VMM_USING_DOMAIN
	unsigned long old_domain;

	old_domain = vmm_context_enter_domain(super_domain_val);
#endif

	cpsr = sp->cpsr;

	if(_bad_cpsr(cpsr)) {
		vmm_info("=================================\n");
		vmm_info("VMM WARING: bad CPSR in guest\n");
		dump_vmm = 1;
	} else {
		if(cpsr & A_Bit && 0) {
			vmm_info("=================================\n");
			vmm_info("VMM WARING: A bit is set in guest\n");
			dump_vmm = 1;
		}

		if((cpsr & I_Bit) && (sp->pc <= VMM_BEGIN)) {
			vmm_info("=================================\n");
			vmm_info("VMM WARING: IRQ disabled in guest\n");
			dump_vmm = 1;
		}

		if(cpsr & F_Bit) {
			vmm_info("=================================\n");
			vmm_info("VMM WARING: FIQ disabled in guest\n");
			dump_vmm = 1;
		}

		if((cpsr & MODEMASK) == USERMODE) {
			if(_vmm_context->virq_status & 1) {
				vmm_info("=================================\n");
				vmm_info("VMM WARING: VIRQ disabled in user mode\n");
				dump_vmm = 1;
			}

			if((sp->pc > 0xbf000000) && (sp->pc < 0xffff0000)) {
				vmm_info("=================================\n");
				vmm_info("VMM WARING: executing kernel code in usr mode\n");
				dump_vmm = 1;
			}

			/* FIXME: when the guest is suspended in user mode and its
			 * interrupts come, this can be misleading. */
#if 0

			if(_vmm_context->virq_pended) {
				vmm_info("=================================\n");
				vmm_info("VMM WARING: VIRQ pended in user mode\n");
				dump_vmm = 1;
			}

#endif
		} else if((cpsr & MODEMASK) == SVCMODE && sp->pc < 0xbf000000) {
			vmm_info("=================================\n");
			vmm_info("VMM WARING: executing usr code in svc mode\n");
			dump_vmm = 1;
		}
	}

#if 0

	if(!vmm_virq_coherence_ok()) {
		vmm_info("=================================\n");
		vmm_info("VMM WARING: bad VIRQ status\n");
		dump_vmm = 1;
	}

#endif

	if(dump_vmm) {
		vmm_show_guest();
		vmm_info("=================================\n");
	}

#ifdef RT_VMM_USING_DOMAIN
	vmm_context_restore_domain(old_domain);
#endif
}

